package ge;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.Logger;

public class ProductionRule {
	public static int MAX_DEPTH = 24;
	private static final Logger LOG = Logger.getLogger(ProductionRule.class);

	private WeakReference<Class<? extends NonTerminal>> _nonterminal = null;
	private List<Expression> _expressions = new ArrayList<Expression>();
	private Boolean _terminates;
	private int _min_termination_depth;

	public ProductionRule(Class<? extends NonTerminal> nonterminal) {
		(new SymbolType(nonterminal)).setProductionRule(this);

		// since the nonterminal is storing a reference to us, we don't want to
		// store a hard reference to the non-terminal
		_nonterminal = new WeakReference<Class<? extends NonTerminal>>(
				nonterminal);
	}

	public ProductionRule(Class<? extends NonTerminal> nonterminal,
			Expression expression) {
		this(nonterminal);

		add(expression);
	}

	public void add(Expression expression) {
		_expressions.add(expression);

		if (expression.containsNonTerminal()) {
			_terminates = false;
		}
	}

	public Expression evaluate(Iterator<Integer> indices, int depth) {
		// Depth = 0 is the first application for StartSymbol, ignoring index.
		int raw_index = (depth == 0 ? 0 : indices.next());
		int index = raw_index % _expressions.size();

		int min_depth = getMinimumTerminationDepth();
		if (depth + min_depth >= MAX_DEPTH) {
			int i = 0, j = 0, expression_number = index + 1;
			do {
				i++;
				int max_depth = _expressions.get(i % _expressions.size())
						.maxTerminationDepth();
				if (max_depth < min_depth) {
					j++;
					index = i % _expressions.size();
				}
			} while (j < expression_number);
			// LOG.trace("depth + min_depth exceeds limit, choosing " +
			// _expressions.get(index) );
		}

		Expression exp = null;

		if (depth < MAX_DEPTH) {
			// System.out.println("Rule[" + index + "][" +
			// _expressions.get(index)
			// + "]");
			// LOG.trace("evaluating @ depth=" + depth + ", index=" + index
			// +":\n\t" + this);
			exp = _expressions.get(index)
					.resolve(indices, raw_index, depth + 1);
		} else {
			throw new MaxRecurstionDepthExceeded(
					"Max recursion depth reached during evaluating [" + this
							+ "] minimumTermantionDepth was: "
							+ getMinimumTerminationDepth());
		}

		return exp;
	}

	/**
	 * Gets the minimum application of all productions rules to terminate this
	 * one. E.g. if this rule contains a terminating expression then the minimum
	 * termination depth is 1.
	 * 
	 * @return
	 */
	public int getMinimumTerminationDepth() {
		// LOG.trace("minTermDeath " + this + "=" + _min_termination_depth);

		if (_min_termination_depth == 0) {
			_min_termination_depth = Integer.MAX_VALUE;

			int tmp = _min_termination_depth;
			for (Expression exp : _expressions) {
				tmp = Math.min(tmp, exp.maxTerminationDepth());
			}

			// The actual termination depth is 1 greater since we have to count
			// ourselves
			_min_termination_depth = tmp + 1;
		}

		return _min_termination_depth;
	}

	public Boolean isTerminator() {
		return _terminates;
	}

	public String toString() {
		StringBuilder str = new StringBuilder();

		String head = _nonterminal.get().getSimpleName();
		str.append('<');
		str.append(head);
		str.append("> ::= ");
		str.append(_expressions.get(0));
		str.append("\n");

		char[] indent = new char[head.length() + 3];
		Arrays.fill(indent, ' ');

		for (int i = 1; i < _expressions.size(); i++) {
			str.append(indent);
			str.append("| ");
			str.append(_expressions.get(i));
			str.append("\n");
		}

		return str.toString();
	}
}
